package ldh.maker.database;

import com.google.gson.Gson;
import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.scene.control.TreeItem;
import ldh.database.*;
import ldh.database.util.MetaUtil;
import ldh.maker.db.TableNoDb;
import ldh.maker.db.TableViewDb;
import ldh.maker.util.FreeMakerUtil;
import ldh.maker.util.UiUtil;
import ldh.maker.vo.TableNo;
import ldh.maker.vo.TableViewData;
import ldh.maker.vo.TreeNode;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.*;
import java.util.Map.Entry;

public class TableInfo {

	private Map<String, Table> tableMap = new TreeMap<String, Table>();
	private List<String[]> tables;
	private String db;
	private Connection connection;
	private Map<String, String> bieming = new HashMap<String, String>();
	private Map<String, TableViewData> tableViewMap = new HashMap<>();

	private volatile boolean isLoadEnd = false;
	private TreeItem<TreeNode> treeItem;

	public TableInfo(Connection connection, String db, TreeItem<TreeNode> treeItem) {
		this(connection, db, treeItem, false);
	}

	public TableInfo(Connection connection, String db, TreeItem<TreeNode> treeItem, boolean isAsych) {
		this.connection = connection;
		this.db = db;
		this.treeItem = treeItem;
		String owner = null;
		asychnLoadTables(connection, db, isAsych, owner);
	}

	private void asychnLoadTables(Connection connection, String db, boolean isAsych, String owner) {
		tables = MetaUtil.getAllTable(connection, owner, db);
//		sequenes = MetaUtil.getAllSequence(connection, owner, db);

		for (String[] tableInfo : tables) {
			tableMap.put(tableInfo[0], new Table(tableInfo[0]));
		}
		if (isAsych) {
			for (String[] tableInfo : tables) {
				loanTable(tableInfo[0]);
			};
			buildAssociation();
			alias();
			noTables();
			isLoadEnd = true;
		} else {;
			Platform.runLater(()->{
				startTask();
			});
		}
	}

	private void noTables() {
		try {
			TableNo tableNo = TableNoDb.loadData(treeItem.getValue().getParent(), db);
			if (tableNo == null) return;
			for (String tableName : tableNo.getTableNoes()) {
				if (tableMap.containsKey(tableName)) {
					tableMap.get(tableName).setCreate(false);
				}
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}

	private void startTask() {
		Task<Void> task = new Task<Void>() {
			@Override protected Void call() throws Exception {
				updateMessage("加载表结构");
				int total = tables.size();
				int i = 0;
				for (String[] tableInfo : tables) {
					i++;
					loanTable(tableInfo[0]);
					updateMessage("加载表第" + i + "张表");
					updateProgress(i, total);
				}
				try {
					buildAssociation();
					alias();
					noTables();
				} catch (Exception e) {
					e.printStackTrace();
				}
				isLoadEnd = true;

				updateProgress(0, 0);
				done();
				return null;
			}
		};

		UiUtil.STATUSBAR.textProperty().bind(task.messageProperty());
		UiUtil.STATUSBAR.progressProperty().bind(task.progressProperty());

		// remove bindings again
		task.setOnSucceeded(event -> {
			UiUtil.STATUSBAR.textProperty().unbind();
			UiUtil.STATUSBAR.progressProperty().unbind();
		});

		new Thread(task).start();
	}

	public void loanTable(String tableName) {
		String[] tableInfo2 = null;
		for (String[] tableInfo : tables) {
			if(tableInfo[0].equals(tableName)) {
				tableInfo2 = tableInfo;
				break;
			}
		}
		if (tableMap.get(tableName).getColumnList() == null) {
			loanTableInfo(db, connection, tableInfo2);
		}
	}

	private void buildAssociation() {
		for (Entry<String, Table> entry : tableMap.entrySet()) {
			Table table = entry.getValue();
			if (table == null) continue;
			if (table.getForeignKeys() != null && table.getForeignKeys().size() > 0) {
				for (ForeignKey fc : table.getForeignKeys()) {
					fc.setForeignTable(tableMap.get(fc.getForeignTableName()));
					if (isOneToOne(table, fc)) {
						fc.setOneToOne(true);
						fc.getColumn().setOneToOne(true);
					}
				}
			}
		}

		//建立多对多关系
		for (Entry<String, Table> entry : tableMap.entrySet()) {
			Table table = entry.getValue();
			if (table == null) continue;
			boolean isMtm = isManyToMany(table);
			if (isMtm) {
				Table one = null, two = null;
				ForeignKey onFk = null, twoFk = null;
				for(ForeignKey fk : table.getForeignKeys()) {
					if (one == null) {
						one = fk.getForeignTable();
						onFk = fk;
					} else {
						two = fk.getForeignTable();
						twoFk = fk;
					}
				}
				ManyToMany mtm = new ManyToMany(one, onFk, two, twoFk, table);
				ManyToMany mtm2 = new ManyToMany(two, twoFk, one, onFk, table);
				one.addManyToMany(mtm);
				two.addManyToMany(mtm2);
				table.setMiddle(true);
			}
		}
	}

	private void loanTableInfo(String db, Connection connection, String[] tableInfo) {
		try {
            String tableName = tableInfo[0];
            String tableComment = tableInfo[1];
            Table table = MetaUtil.getTable(connection, "%", db, tableName);
			tableMap.put(tableName, table);//改变column对应java的类型
            if (table.getPrimaryKey() == null) {
				return;
			}

            table.setComment(tableComment);

        } catch(Exception e) {
            e.printStackTrace();
        }
	}

	//即使外键也是索引的为多对多的中间表
	private boolean isManyToMany(Table table) {
		return table.getForeignKeys().size() == 2 && table.getColumnList().size() <= 3;
	}
	
	private boolean isOneToOne(Table table, ForeignKey fk) {
		if (table.getIndexies()!= null) {
			for (UniqueIndex ic : table.getIndexies()) {
				if (table.getForeignKeys() != null && ic.getColumns().size() == 1) {
					for (String name : ic.getColumnNames()) {
						if (name.equals(fk.getColumn().getName())) {
							return true;
						}
					}
					
				}
			}
		}
		return false;
	}

	public Table getTable(String tableName) {
		return tableMap.get(tableName);
	}
	
	public Map<String, Table> getTables() {
		return tableMap;
	}

	//起别名
	private void alias() {
		int size = 0;
		try {
			if (treeItem != null) {
				List<TableViewData> tableViewDatas = TableViewDb.loadData(treeItem.getParent().getValue(), db);
				for (TableViewData data : tableViewDatas) {
					if (!getTables().containsKey(data.getTableName())) continue;
					tableViewMap.put(data.getTableName(), data);
					getTables().get(data.getTableName()).setAlias(data.getAlias());
					bieming.put(data.getTableName(), data.getAlias());
				}
				size = tableViewDatas.size();
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}
		for (Entry<String, Table> entry : getTables().entrySet()) {
			aliaTable(entry.getValue());
		}
		if (size == 0) {
			for (Entry<String, Table> entry : getTables().entrySet()) {
				TableViewData tableViewData = new TableViewData();
				tableViewData.setTreeNodeId(treeItem.getValue().getParent().getId());
				tableViewData.setDbName(treeItem.getValue().getData().toString());
				tableViewData.setTableName(entry.getKey());
				tableViewData.setAlias(entry.getValue().getAlias());
				List<TableViewData.ColumnData> columnDatas = new ArrayList<>();
				for (Column column : entry.getValue().getColumnList()) {
					TableViewData.ColumnData columnData = new TableViewData.ColumnData();
					columnData.setColumnName(column.getName());
					columnData.setShow(true);
					columnDatas.add(columnData);
				}
				Gson gson = new Gson();
				String json = gson.toJson(columnDatas);
				tableViewData.setColumns(json);
				try {
					TableViewDb.save(tableViewData);
					tableViewMap.put(tableViewData.getTableName(), tableViewData);
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
		}
	}

	private void aliaTable(Table table) {
		if (table.getAlias() != null) return;
		String tableName = table.getName();
		String tn = table.getJavaName();
		StringBuilder sb = new StringBuilder();
		for (int i=0, l=tn.length(); i<l; i++) {
			if (i == 0) {
				sb.append(String.valueOf(tn.charAt(i)).toLowerCase());
			} else if (Character.isUpperCase(tn.charAt(i))) {
				sb.append(String.valueOf(tn.charAt(i)).toLowerCase());
			}
		}

		String aliasName = sb.toString();
		int j=0;
		String temp = aliasName;
		while (bieming.containsValue(temp)) {
			temp = aliasName + j++;
		}
		table.setAlias(temp);
		bieming.put(tableName, temp);
	}

	public boolean isLoadEnd() {
		return isLoadEnd;
	}

	public Map<String, TableViewData> getTableViewDataMap() {
		return tableViewMap;
	}

}
